package com.vonchenchen.simple_downloader.downloader;

import android.content.Context;
import android.os.Environment;

import java.io.File;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * Created by lidechen on 9/14/16.
 */
public class DownLoadManager {

    private static Context mContext;

    private static final int THREAD_NUM = 5;
    private static final int TASK_NUM = 3;
    private static final int THREAD_NUM_PER_TASK = 2;

    private static final int CORES = Runtime.getRuntime().availableProcessors();
    private static final int POOL_SIZE = CORES + 1;
    private static final int POOL_SIZE_MAX = CORES * 2 + 1;

    private static final BlockingQueue<Runnable> POOL_QUEUE_TASK = new LinkedBlockingQueue<>(56);
    private static final BlockingQueue<Runnable> POOL_QUEUE_THREAD = new LinkedBlockingQueue<>(256);

    private static final ThreadFactory TASK_FACTORY = new ThreadFactory() {
        private final AtomicInteger COUNT = new AtomicInteger(1);

        @Override
        public Thread newThread(Runnable runnable) {
            return new Thread(runnable, "DLTask #" + COUNT.getAndIncrement());
        }
    };

    private static final ThreadFactory THREAD_FACTORY = new ThreadFactory() {
        private final AtomicInteger COUNT = new AtomicInteger(1);

        @Override
        public Thread newThread(Runnable runnable) {
            return new Thread(runnable, "DLThread #" + COUNT.getAndIncrement());
        }
    };

    public static String PATH_DOWNLOAD;
    public static String PATH_DOWNLOAD_INFO;
    private static File sDwonLoaderFile;
    private static File sDwonLoadInfoFile;

    /** 线程列表 */
    private static LinkedList<ThreadInfo> mIdleDownLoadThreadList = new LinkedList<ThreadInfo>();
    /** 正在工作的线程列表 */
    private static LinkedList<ThreadInfo> mBusyDownLoadThreadList = new LinkedList<ThreadInfo>();

    /** 下载任务等待对列 */
    private static LinkedList<DownLoadInfo> mWaitDownLoadTaskQueue = new LinkedList<DownLoadInfo>();
    /** 正在进行的下载任务 */
    private static LinkedList<DownLoadInfo> mBusyDownLoadTaskList = new LinkedList<DownLoadInfo>();

    /** 任务线程迟 */
    private static final ExecutorService POOL_TASK = new ThreadPoolExecutor(POOL_SIZE,
            POOL_SIZE_MAX, 3, TimeUnit.SECONDS, POOL_QUEUE_TASK, TASK_FACTORY);
    /** 线程线程池 */
    private static final ExecutorService POOL_Thread = new ThreadPoolExecutor(POOL_SIZE * 5,
            POOL_SIZE_MAX * 5, 1, TimeUnit.SECONDS, POOL_QUEUE_THREAD, THREAD_FACTORY);

    public static void init(Context context){

        mContext = context;

        FileUtils.init(mContext);

        //创建一般日志路径
        if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {// 优先保存到SD卡中
            PATH_DOWNLOAD = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "com.vonchenchen.download/";
        } else {// 如果SD卡不存在，就保存到本应用的目录下
            PATH_DOWNLOAD = mContext.getFilesDir().getAbsolutePath() + File.separator + "com.vonchenchen.download/";
        }
        sDwonLoaderFile = new File(PATH_DOWNLOAD);
        if (!sDwonLoaderFile.exists()) {
            sDwonLoaderFile.mkdirs();
        }

        PATH_DOWNLOAD_INFO = PATH_DOWNLOAD+"download_info/";
        sDwonLoadInfoFile = new File(PATH_DOWNLOAD_INFO);
        if (!sDwonLoadInfoFile.exists()) {
            sDwonLoadInfoFile.mkdirs();
        }

        DownLoadInfo.POOL_TASK = POOL_TASK;
        ThreadInfo.POOL_Thread = POOL_Thread;

        DownLoadInfo.mBusyDownLoadTaskList = mBusyDownLoadTaskList;

        ThreadInfo.mBusyDownLoadThreadList = mBusyDownLoadThreadList;
        ThreadInfo.mIdleDownLoadThreadList = mIdleDownLoadThreadList;

        initData();
    }

    /**
     * 初始化线程控制块
     */
    private static void initData(){

        for(int i=0; i<THREAD_NUM; i++){
            ThreadInfo threadInfo = new ThreadInfo();
            threadInfo.threadId = i;

            mIdleDownLoadThreadList.add(threadInfo);
        }
    }

    public static synchronized void downLoadFromUrl(String url, String path){

        //创建一个下载控制块
        DownLoadInfo downLoadInfo = new DownLoadInfo();
        downLoadInfo.localPath = path;
        downLoadInfo.urlStr = url;

        //将下载控制快加入下载任务对列
        mWaitDownLoadTaskQueue.addLast(downLoadInfo);

        //进行一次下载任务调度
        taskSchedule();
    }

    /**
     * 重新进行线程调度
     */
    public static synchronized void taskSchedule(){

        //检查正在执行的下载任务数量
        int taskNum = mBusyDownLoadTaskList.size();

        int threadNum = checkIdleThreadNum();

        //如果任务执行队列有空闲, 且任务等待队列不为空, 且还有空余下载线程, 则将等待对列中的任务放到任务执行对列中,知道填满
        while(taskNum <= TASK_NUM && mWaitDownLoadTaskQueue.size() > 0 && threadNum > 0){

            //任务对列出队
            DownLoadInfo downLoadInfo = mWaitDownLoadTaskQueue.removeFirst();
            //给任务添加一个实际执行对象
            downLoadInfo.mDownLoadTask = new DownLoadTask();

            //给任务设置线程
            downLoadInfo.mThreadInfoMap = new HashMap<Integer, ThreadInfo>();
            //线程数不超过THREAD_NUM_PER_TASK ,同时不能大于threadNum
            for(int j=0; j<(threadNum > THREAD_NUM_PER_TASK?THREAD_NUM_PER_TASK:threadNum); j++){
                ThreadInfo threadInfo = mIdleDownLoadThreadList.removeFirst();
                threadInfo.downLoadThread = new DownLoadThread();
                downLoadInfo.mThreadInfoMap.put(threadInfo.threadId, threadInfo);
            }

            //mBusyDownLoadTaskList.add(downLoadInfo);
            downLoadInfo.addToDownLoadTask();
            downLoadInfo.execDownLoadTask();

            taskNum = mBusyDownLoadTaskList.size();
            threadNum = checkIdleThreadNum();
        }
    }

    private static int checkIdleThreadNum(){
        return mIdleDownLoadThreadList.size();
        //return 10;
    }

    public static File getDwonLoaderFile(){
        return sDwonLoaderFile;
    }

    public static File getDownLoadInfoFile(){
        return sDwonLoadInfoFile;
    }
}
